001 /*
002 * Copyright 2005 Stephen J. McConnell
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
013 * implied.
014 *
015 * See the License for the specific language governing permissions and
016 * limitations under the License.
017 */
018
019 package net.dpml.tools.tasks;
020
021 import java.io.File;
022 import java.io.OutputStream;
023 import java.io.FileOutputStream;
024 import java.io.OutputStreamWriter;
025 import java.io.Writer;
026 import java.io.IOException;
027 import java.net.URI;
028 import java.net.URL;
029
030 import net.dpml.lang.Version;
031
032 import net.dpml.library.Module;
033 import net.dpml.library.Resource;
034 import net.dpml.library.Type;
035
036 import net.dpml.transit.Artifact;
037 import net.dpml.transit.artifact.ArtifactNotFoundException;
038 import net.dpml.transit.link.ArtifactLinkManager;
039
040 import org.apache.tools.ant.BuildException;
041 import org.apache.tools.ant.Project;
042 import org.apache.tools.ant.types.FileSet;
043 import org.apache.tools.ant.taskdefs.Copy;
044 import org.apache.tools.ant.taskdefs.Checksum;
045
046 /**
047 * Execute the install phase.
048 *
049 * @author <a href="http://www.dpml.net">Digital Product Meta Library</a>
050 * @version 1.1.1
051 */
052 public class InstallTask extends GenericTask
053 {
054 /**
055 * Execute the project.
056 * @exception BuildException if a build errror occurs
057 */
058 public void execute() throws BuildException
059 {
060 installDeliverables();
061 }
062
063 private void installDeliverables()
064 {
065 Resource resource = getResource();
066 String resourceVersion = resource.getVersion();
067 boolean snapshot = "SNAPSHOT".equals( resourceVersion );
068 boolean bootstrap = "BOOTSTRAP".equals( resourceVersion );
069 boolean validation = resource.getBooleanProperty( "project.validation.enabled", false );
070 boolean validate = !snapshot && !bootstrap && validation;
071 Type[] types = resource.getTypes();
072 if( types.length == 0 )
073 {
074 return;
075 }
076
077 final File deliverables = getContext().getTargetDeliverablesDirectory();
078 for( int i=0; i < types.length; i++ )
079 {
080 Type type = types[i];
081 checkType( resource, type, validate );
082 }
083
084 if( deliverables.exists() )
085 {
086 log( "Installing deliverables from [" + deliverables + "]", Project.MSG_VERBOSE );
087 final File cache = (File) getProject().getReference( "dpml.cache" );
088 log( "To cache dir [" + cache + "]", Project.MSG_VERBOSE );
089 try
090 {
091 final FileSet fileset = new FileSet();
092 fileset.setProject( getProject() );
093 fileset.setDir( deliverables );
094 fileset.createInclude().setName( "**/*" );
095 Module parent = resource.getParent();
096 if( null == parent )
097 {
098 copy( cache, fileset, true );
099 }
100 else
101 {
102 final String group = parent.getResourcePath();
103 final File destination = new File( cache, group );
104 copy( destination, fileset, true );
105 }
106 }
107 catch( Throwable e )
108 {
109 final String error =
110 "Unexpected error while constructing ant fileset."
111 + "\nDeliverables dir: " + deliverables;
112 throw new BuildException( error, e );
113 }
114 }
115 }
116
117 private void checkType( Resource resource, Type type, boolean validate )
118 {
119 //
120 // Check that the project has actually built the resource
121 // type that it declares
122 //
123
124 String id = type.getID();
125 String filename = getContext().getLayoutFilename( id );
126 final File deliverables = getContext().getTargetDeliverablesDirectory();
127 File group = new File( deliverables, id + "s" );
128 File target = new File( group, filename );
129 if( !target.exists() && !id.equalsIgnoreCase( "null" ) )
130 {
131 final String error =
132 "Project ["
133 + resource
134 + "] declares that it produces the resource type ["
135 + id
136 + "] however no artifacts of that type are present in the target deliverables directory.";
137 throw new BuildException( error, getLocation() );
138 }
139
140 //
141 // If the type declares an alias then construct a link
142 // and add the link to the deliverables directory as part of
143 // install process.
144 //
145
146 Version version = type.getVersion();
147 if( null != version )
148 {
149 try
150 {
151 Artifact artifact = resource.getArtifact( id );
152 String uri = artifact.toURI().toASCIIString();
153 String link = null;
154 if( Version.NULL_VERSION.equals( version ) )
155 {
156 link = resource.getName() + "." + id + ".link";
157 }
158 else
159 {
160 link = resource.getName()
161 + "-"
162 + version.getMajor()
163 + "."
164 + version.getMinor()
165 + "."
166 + id + ".link";
167 }
168 File out = new File( group, link );
169 boolean flag = true;
170 if( out.exists() )
171 {
172 ArtifactLinkManager manager = new ArtifactLinkManager();
173 URI enclosed = manager.getTargetURI( new URI( out.toURL().toString() ) );
174 if( artifact.toURI().equals( enclosed ) )
175 {
176 flag = false;
177 }
178 }
179
180 if( flag )
181 {
182 final String message =
183 link.toString()
184 + "\n target: "
185 + uri.toString();
186 log( message, Project.MSG_VERBOSE );
187 out.createNewFile();
188 final OutputStream output = new FileOutputStream( out );
189 final Writer writer = new OutputStreamWriter( output );
190 writer.write( uri );
191 writer.close();
192 output.close();
193 }
194 }
195 catch( Exception e )
196 {
197 final String error =
198 "Internal error while attempting to create a link for the resource type ["
199 + id
200 + "] in project ["
201 + resource
202 + "].";
203 throw new BuildException( error, e, getLocation() );
204 }
205 }
206
207 if( validate )
208 {
209 validateType( resource, type, target );
210 }
211 }
212
213 private void validateType( Resource resource, Type type, File target )
214 {
215 String id = type.getID();
216 try
217 {
218 Artifact artifact = resource.getArtifact( id );
219 URL url = artifact.toURL();
220 File file = (File) url.getContent( new Class[]{File.class} );
221 if( file.exists() )
222 {
223 log( "validating " + target.getName() );
224 compare( file, target, id );
225 }
226 }
227 catch( ArtifactNotFoundException anfe )
228 {
229 // continue as there is nothing to compare with
230 }
231 catch( IOException ioe )
232 {
233 final String error =
234 "IO error while attempting to cross-check resource type: " + id
235 + "\n" + ioe.toString();
236 throw new BuildException( error, ioe, getLocation() );
237 }
238 }
239
240 private void compare( File old, File target, String id )
241 {
242 String oldValue = getChecksum( old );
243 String newValue = getChecksum( target );
244 if( !oldValue.equals( newValue ) )
245 {
246 String path = getContext().getLayoutFilename( id );
247 final String error =
248 "A versioned resource created in this build has a different MD5 signature "
249 + "compared to an existing resource of the same name in the cache directory. "
250 + "If the cached resource is a published resource a possibility exists that "
251 + "this build artifact will be introducing a modification to an existing published "
252 + "contract. If the resource has not been published then you can rebuild without "
253 + "deliverable validation. Otherwise, consider assigning an alternative "
254 + "(non-conflicting) version identifier."
255 + "\n"
256 + "\n\tProduced Type: " + path
257 + "\n\tCached Resource: " + getCanonicalPath( old )
258 + "\n";
259 throw new BuildException( error, getLocation() );
260 }
261 }
262
263 private String getCanonicalPath( File file )
264 {
265 try
266 {
267 return file.getCanonicalPath();
268 }
269 catch( IOException e )
270 {
271 final String error =
272 "Internal error while attempting to resolve a canonical path for the file: " + file;
273 throw new BuildException( error, e, getLocation() );
274 }
275 }
276
277 private String getChecksum( File file )
278 {
279 final String key = "checksum.property." + file.toString();
280 final Checksum checksum = (Checksum) getProject().createTask( "checksum" );
281 checksum.setTaskName( getTaskName() );
282 checksum.setFile( file );
283 checksum.setProperty( key );
284 checksum.init();
285 checksum.execute();
286 return getProject().getProperty( key );
287 }
288
289 /**
290 * Utility operation to copy a fileset to a destination directory.
291 * @param destination the destination directory
292 * @param fileset the fileset to copy
293 * @param preserve the preserve timestamp flag
294 */
295 public void copy( final File destination, final FileSet fileset, boolean preserve )
296 {
297 mkDir( destination );
298 final Copy copy = (Copy) getProject().createTask( "copy" );
299 copy.setTaskName( getTaskName() );
300 copy.setPreserveLastModified( preserve );
301 copy.setTodir( destination );
302 copy.addFileset( fileset );
303 copy.setOverwrite( true ); // required for filtered deliverables
304 copy.init();
305 copy.execute();
306 }
307 }